"""
@File    :   no_introduction.py
@Time    :   2024/03/28 22:25:23
@Author  :   Qrj
@Email   :   923207736@qq.com
@description   :   无需引入其他业务模块依赖(全局的no_introduction.py下的依赖项不包含在内)的依赖
"""

from pathlib import Path
from typing import Annotated

from fastapi import Body, Depends, Query, Request
from fastapi.security.utils import get_authorization_scheme_param
from sqlalchemy.ext.asyncio import AsyncSession

from src.business.admin.system.menu.service import MenuService
from src.business.admin.system.oauth2.auth.enums import OAuth2GrantTypeEnum
from src.business.admin.system.oauth2.token.exceptions import (
    access_token_expired_exception,
    menu_permission_not_enough_exception,
)
from src.business.admin.system.oauth2.token.schemas.out import (
    AccessTokenCompleteOutSchema,
)
from src.business.admin.system.oauth2.token.service import TokenService
from src.business.admin.system.tenant.package.service import TenantPackageService
from src.business.admin.system.tenant.tenant.service import TenantService
from src.business.admin.system.user.service import UserService
from src.dependencies.no_introduction import depend_get_master_db_session
from src.exceptions.custom_async_validator import generate_async_validator_exception
from src.utils.path import PathUtil


class GetAccessTokenDependencies:
    """获取访问令牌的依赖类"""

    @staticmethod
    async def from_header(request: Request) -> str:
        authorization = request.headers.get("Authorization")
        scheme, param = get_authorization_scheme_param(authorization)
        if not authorization or scheme.lower() != "bearer":
            raise access_token_expired_exception
        return param

    @staticmethod
    async def from_query(
        token: Query(default=..., min_length=1, max_length=255, description="访问令牌"),
    ) -> str:
        return token


class ValidateAccessTokenExpiredAndReturnDependencies:
    """验证访问令牌是否过期的依赖类"""

    @staticmethod
    async def from_header(
        access_token: Annotated[str, Depends(GetAccessTokenDependencies.from_header)],
    ) -> AccessTokenCompleteOutSchema:
        # 只从redis缓存中获取access_token
        # 没获取到的话会直接返回401错误, 客户端可使用刷新令牌来获取新的访问令牌
        access_token_info: AccessTokenCompleteOutSchema = (
            await TokenService.get_access_token(access_token)
        )
        # 未过期则返回
        return access_token_info


class ValidateAccessTokenTenantAndReturnDependencies:
    """验证访问令牌对应的租户是否存在、是否禁用、是否过期
    1. 纠正租户对应的租户套餐ID
    2. 纠正租户对应的是否系统级别租户
    """

    @staticmethod
    async def from_header(
        access_token: Annotated[
            AccessTokenCompleteOutSchema,
            Depends(ValidateAccessTokenExpiredAndReturnDependencies.from_header),
        ],
        db_session: Annotated[AsyncSession, Depends(depend_get_master_db_session)],
    ) -> AccessTokenCompleteOutSchema:
        # 验证访问令牌对应的租户是否存在、是否禁用、是否过期
        tenant = await TenantService.get_tenant_use_id(
            db_session, access_token.tenant_id
        )
        # 纠正租户对应的租户套餐ID
        if access_token.tenant_pakcage_id != tenant.tenant_package_id:
            access_token.tenant_pakcage_id = tenant.tenant_package_id
        # 纠正租户对应的是否系统级别租户
        if access_token.tenant_is_system != tenant.is_system:
            access_token.tenant_is_system = tenant.is_system
        return access_token


class GetTenantIdFromAccessTokenDependencies:
    """从访问令牌中获取经验证后确认存在的租户ID"""

    @staticmethod
    async def from_header(
        access_token: Annotated[
            AccessTokenCompleteOutSchema,
            Depends(ValidateAccessTokenTenantAndReturnDependencies.from_header),
        ],
    ) -> int:
        return access_token.tenant_id


class ValidateAccessTokenUserAndReturnDependencies:
    """验证访问令牌对应的用户是否存在、是否禁用
    1. 对username进行纠正
    2. 对is_admin进行纠正
    """

    @staticmethod
    async def from_header(
        access_token: Annotated[
            AccessTokenCompleteOutSchema,
            Depends(ValidateAccessTokenTenantAndReturnDependencies.from_header),
        ],
        db_session: Annotated[AsyncSession, Depends(depend_get_master_db_session)],
    ) -> AccessTokenCompleteOutSchema:
        if (
            access_token.oauth2_grant_type == OAuth2GrantTypeEnum.password
            or access_token.oauth2_grant_type == OAuth2GrantTypeEnum.swagger_password
        ):
            # 密码模式(包括swagger专用密码模式)
            # 验证访问令牌对应的用户是否存在、是否禁用
            user_obj = await UserService.get_user_use_id(
                db_session, access_token.tenant_id, access_token.user_id
            )
            if access_token.username != user_obj.username:
                access_token.username = user_obj.username
        return access_token


class GetUserNameFromAccessTokenDependencies:
    """从访问令牌中获取经纠正过的用户名"""

    @staticmethod
    async def from_header(
        access_token: Annotated[
            AccessTokenCompleteOutSchema,
            Depends(ValidateAccessTokenUserAndReturnDependencies.from_header),
        ],
    ) -> str:
        return access_token.username


class GetUserIdFromAccessTokenDependencies:
    """从访问令牌中获取经验证后确认存在的用户ID"""

    @staticmethod
    async def from_header(
        access_token: Annotated[
            AccessTokenCompleteOutSchema,
            Depends(ValidateAccessTokenUserAndReturnDependencies.from_header),
        ],
    ) -> int:
        return access_token.user_id


class ValidateAndGetAccessTokenMenuPermissionDependencies:
    """验证并获取当前访问令牌对应菜单权限的依赖类
    验证的东西:
    1. 验证租户是否存在、是否禁用、是否过期
    2. 对于password模式, 验证用户是否存在、是否禁用
    """

    @staticmethod
    async def from_header(
        access_token: Annotated[
            AccessTokenCompleteOutSchema,
            Depends(ValidateAccessTokenUserAndReturnDependencies.from_header),
        ],
        db_session: Annotated[AsyncSession, Depends(depend_get_master_db_session)],
    ) -> tuple[list[str], AccessTokenCompleteOutSchema]:
        # 按照访问令牌中的OAuth2授权方式来区分菜单权限的获取方式
        if (
            access_token.oauth2_grant_type == OAuth2GrantTypeEnum.password
            or access_token.oauth2_grant_type == OAuth2GrantTypeEnum.swagger_password
        ):
            # 密码模式(包括swagger专用密码模式)通过访问令牌指向的用户的所有角色来获取用户所拥有的所有菜单权限
            # 获取用户的所有菜单权限
            menu_permission_list = await UserService.get_user_menu_permission(
                db_session, access_token.tenant_id, access_token.user_id
            )
        else:
            # 其他OAuth2模式通过访问令牌的OAuth2授权范围参数来获取令牌拥有的菜单权限
            # 通过OAuth2授权范围参数来获取令牌拥有的菜单权限
            menu_permission_list = access_token.oauth2_scopes
        return menu_permission_list, access_token


class BaseDependVerifyAccessTokenMenuPermission:
    """验证当前访问令牌菜单权限的依赖基础类"""

    def __init__(self, file_path: str, operation: str):
        """构建所需的菜单权限

        Args:
            file_path (str): 必须固定使用__file__
            operations: 操作类型
        """
        # 获取业务路径
        business_path_obj = PathUtil.get_business_path()
        # 获取文件路径
        file_path_obj = Path(file_path)
        # 找到文件路径所属的业务模块
        if file_path_obj.is_file():
            # 先找到父文件夹
            file_path_obj = file_path_obj.parent
        if file_path_obj.name in [
            "router",
            "service",
            "utils",
            "schemas",
            "models",
            "dependencies",
            "enums",
            "exceptions",
        ]:
            # 如果文件夹的名称是特定的名称, 则再找父文件夹路径
            file_path_obj = file_path_obj.parent
        # 获取实例化本依赖类的文件路径与业务路径的相对路径
        relative_path = file_path_obj.relative_to(business_path_obj).as_posix()
        # 生成权限字符串
        permission_path_str = relative_path.replace("/", "@")
        self.need_permission_str: str = permission_path_str + ":" + operation


class DependVerifyAccessTokenMenumPermission(BaseDependVerifyAccessTokenMenuPermission):
    """验证当前访问令牌菜单权限的依赖类(需要need_permission_str权限)"""

    async def __call__(
        self,
        access_token_and_menu_permission_list: Annotated[
            tuple[list[str], AccessTokenCompleteOutSchema],
            Depends(ValidateAndGetAccessTokenMenuPermissionDependencies.from_header),
        ],  # 获取访问令牌权限列表与访问令牌
        db_session: Annotated[AsyncSession, Depends(depend_get_master_db_session)],
    ):
        # 拆分出"访问令牌权限列表"和"访问令牌"
        (
            access_token_permission_list,
            access_token,
        ) = access_token_and_menu_permission_list
        # 验证菜单是否存在或被禁用
        a = await MenuService.get_menu_use_permission(
            db_session, self.need_permission_str
        )

        # 验证租户套餐是否包含该菜单
        if access_token.tenant_is_system:
            # 系统级别租户无需验证
            pass
        else:
            # 验证租户套餐是否有菜单
            tenant_package = await TenantPackageService.get_tenant_package_use_id(
                db_session, access_token.tenant_pakcage_id
            )
            if self.need_permission_str not in tenant_package.menu_permission:
                # 租户套餐不包含该菜单直接报错
                raise generate_async_validator_exception(
                    "tenant_id", "当前租户未分配相关菜单"
                )

        # 管理员用户无需检测菜单权限
        if access_token.user_is_admin:
            return

        # 普通身份菜单权限检查, 权限不足直接报错
        if self.need_permission_str not in access_token_permission_list:
            raise menu_permission_not_enough_exception


class GetRefreshTokenDependencies:
    """获取刷新令牌的依赖类"""

    @staticmethod
    async def from_body(
        refresh_token: Annotated[
            str, Body(..., min_length=1, max_length=255, embed=True)
        ],
    ) -> str:
        return refresh_token
